structurizr / export

Export models and views to external formats.
https://docs.structurizr.com/export
Apache License 2.0
18 stars 28 forks source link

Allow to export sprites to PlantUML #11

Closed klu2 closed 2 years ago

klu2 commented 2 years ago

PlantUML allows to pass sprites to diagrams in order to have all kinds of logos in the generated diagrams, see https://github.com/plantuml-stdlib/C4-PlantUML#getting-started

In the code that looks like that:

@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml

!define DEVICONS https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons
!define FONTAWESOME https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/font-awesome-5
!include DEVICONS/angular.puml
!include DEVICONS/java.puml
!include DEVICONS/msql_server.puml
!include FONTAWESOME/users.puml

LAYOUT_WITH_LEGEND()

Person(user, "Customer", "People that need products", $sprite="users")
Container(spa, "SPA", "angular", "The main interface that the customer interacts with", $sprite="angular")
Container(api, "API", "java", "Handles all business logic", $sprite="java")
ContainerDb(db, "Database", "Microsoft SQL", "Holds product, order and invoice information", $sprite="msql_server")

Rel(user, spa, "Uses", "https")
Rel(spa, api, "Uses", "https")
Rel_R(api, db, "Reads/Writes")
@enduml

which results in such a diagram:

image

It would be great to have that in the C4PlantUMLExporter.

Adding separate includes is already possible via com.structurizr.export.plantuml.AbstractPlantUMLExporter#PLANTUML_INCLUDES_PROPERTY, what we need to achieve is to add the sprites somehow from the model.

I thought a bit how that could be integrated into the method com.structurizr.export.plantuml.C4PlantUMLExporter#writeElement without breaking things. We could of course pass the content of container.getTechnology() or component.getTechnology() as sprite to the writer but that will make the diagram look weird when no sprite can be found for a given technology (besides that I don't know if a technology is always the best indicator for a sprite).

I'd therefore extract the code which finally writes the Container or Component to the writer to a strategy interface which can be exchanged by clients, leaving the default-implementation as-is in the current repo in order to not break existing things.

If you want I can provide a PR how that could look like.

Probably I could catch https://github.com/structurizr/export/issues/2 then as well.

klu2 commented 2 years ago

Ah, probably even better would be to connect the sprite to ElementStyle.getIcon, that would be quite generic, and we could easily add that to existing code without breaking anything - it would also conceptually make sense.

Ok for you if I create a PR for that?

klu2 commented 2 years ago

3rd option would be to use AddElementTag (which we could/should use anyways to also apply all other kinds of stylings) and pass the icon to the sprite here:

!define DEVICONS https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons
!include DEVICONS/msql_server.puml
!include DEVICONS/angular.puml

AddElementTag("MSQL", $sprite="msql_server")
AddElementTag("Angular", $sprite="angular")

: 

Container(InternetBankingSystem.SinglePageApplication, "Single-Page Application", "JavaScript and Angular", "Provides all of the Internet banking functionality to customers via their web browser.", $tags="Element+Container+Web Browser+Angular")
ContainerDb(InternetBankingSystem.Database, "Database", "Oracle Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.", $tags="Element+Container+Database+MSQL")

(and some more code) would result in:

image

And we could also use the properties from https://github.com/plantuml-stdlib/C4-PlantUML#custom-tagsstereotypes-support-and-skinparam-updates to pass all other kinds of properties (colors, bgcolor,...)

klu2 commented 2 years ago

Hm. ElementStyle.setIcon requires either a URL or a base64-encoded JPG or PNG as of ImageUtils.validateImage, so passing the sprite name here is unfortunately no option

simonbrowndotje commented 2 years ago

I've had discussions with others about support for sprites, and the most logical way to do this, and what everybody expects to happen, is to use whatever PNG/GIF icon is set for the element style (the icon property). This will require converting the specified PNG/GIF to a sprite, which is a feature supported by PlantUML (java -jar plantuml.jar -encodesprite 16z foo.png; see https://plantuml.com/sprite). Feel free to submit a PR for this, but please don't add a dependency on the GPL version of PlantUML.

And you're also correct, another alternative that doesn't require any code changes is to create your own include that defines a number of tags. For example, this will work now -> demo.

include.puml

!define DEVICONS https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons
!include DEVICONS/msql_server.puml

AddElementTag("MSQL", $sprite="msql_server")

workspace.dsl

workspace "Getting Started" "This is a model of my software system." {

    model {
        softwareSystem = softwareSystem "Software System" {
            tags "MSQL"
        }
    }

    views {
        properties {
            "plantuml.includes" "https://gist.githubusercontent.com/simonbrowndotje/70c5a3ca611d06e6247f2a7ba936901c/raw/3026104850ff55fd13cb9d75b51e1c7b9e786ee7/include.puml"
        }
        systemLandscape {
            include *
            autoLayout
        }
    }

}

image

klu2 commented 2 years ago

Well, that would require users to publish their .puml files to a webserver, that makes it complicated.

What about reusing ModelItem.addProperty ?

Code could look like that (in Java):

Container container = softwareSystem.addContainer("Web Application", "", "Angular");
container.addProperty(C4PlantUMLExporter.PLANTUML_SPRITE_PROPERTY, "angular");

workspace.views.addProperty(
    C4PlantUMLExporter.PLANTUML_INCLUDES_PROPERTY,
    "https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons/angular.puml"
);

And in C4PlantUMLExporter.writeElement you check the properties of the current ModelItem, and if a key C4PlantUMLExporter.PLANTUML_SPRITE_PROPERTY exists, then we add the $sprite there directly. That would also have the big advantage that we would not extensively create tags (which would be shown in the legend then) just to draw some icons.

And when doing that, we could take care of https://github.com/structurizr/export/issues/2 as well (and remove the sprite-property then)

Nevertheless, we could/should add support for AddElementTag as described above as well, but that could go to a separate ticket as well.

What do you think?

simonbrowndotje commented 2 years ago

Well, that would require users to publish their .puml files to a webserver, that makes it complicated.

I think !include can reference local files, and I imagine that most users have PlantUML installed locally anyway.

What about reusing ModelItem.addProperty ?

I'd rather not do that, as (1) it mixes content with presentation and (2) it bypasses the existing element style mechanism, which is what everybody is expecting to use.

My preference would be to automatically convert the element style icon to a sprite, since that's what users are already expecting should happen. If that's too much work, I'd be happy to add the ability to set name/value properties on the ElementStyle and RelationshipStyle classes, which you could use for setting sprite names.

klu2 commented 2 years ago

I imagine that most users have PlantUML installed locally anyway.

Not in our case - we would use a Confluence macro to render the puml files, and those need to be self-contained (or reference data from a webserver)

I'd be happy to add the ability to set name/value properties on the ElementStyle and RelationshipStyle classes, which you could use for setting sprite names.

If you could do that in structurizr-core, that would be great - I'll add support then in the exporter.

klu2 commented 2 years ago

I saw you added that already to structurizr-core (https://github.com/structurizr/java/commit/783ff48f0251ceea966e6fd61397913b5837e441), still waiting for the release to MavenCentral - do you have a time plan for that already?

simonbrowndotje commented 2 years ago

No release plan yet. BTW, did you see the following?

klu2 commented 2 years ago

yes, I'm currently about to release a separate java library containing Icons of all kinds as URL, Base64-Strings and PlantUML-Sprites, and URLs and Base64 versions could then also be injected to ElementStyle.setIcon(), so basically we would not have the need for Sprites at all then.

ggrossetie commented 2 years ago

@klu2 Are you still working on it? I want to give it a try using $sprite="img:data:image/png;base64,...."

klu2 commented 2 years ago

@klu2 Are you still working on it? I want to give it a try using $sprite="img:data:image/png;base64,...."

I published https://github.com/cloudflightio/structurizr-export-c4plantuml which allows you to use not only tags and properties, but you can use that also to apply themes. Check the readme there, it comes with an example.

simonbrowndotje commented 2 years ago

Support for sprites has now been added; see https://github.com/structurizr/export/issues/21 for details.