FLOSS Project Planets

Getting ready for Akademy 2024

Planet KDE - Sat, 2024-08-31 03:45

In less than a week this year’s Akademy starts in Würzburg, Germany, and as usual I’m very much looking forward to that :)

Talks

Akademy starts with two days full of presentations. Online participation is possible as well, you can register here.

I have two talks myself.

KDE’s CI and CD Infrastructure

Sunday, 14:30 in room 1, together with Ben, Hannah and Julius, details.

We want to give an overview of the many parts and options we have around continuous integration and deployment since the migration from Jenkins to Gitlab, to enable more people to do changes and adjustments there themselves.

Finding your way around Akademy - Venue maps and indoor routing in Kongress

Saturday, 18:00 in room 1, details.

A lightning talk about the use of OSM indoor venue maps in our conference companion app Kongress.

BoFs

Following the talks there will be three days of meetings on various topics (“BoFs”). There’s fewer BoFs I’m (co-)hosting or otherwise directly involved in than last year:

  • KDE PIM (Monday 14:00)
  • CI/CD (Tuesday 15:00)

While that should be a bit less stressful, those will be very busy days nevertheless, with already now too many sessions on the schedule I’d like to participate in.

And of course every event essentially is just one big Itinerary BoF in disguise.

Qt Contributor Summit

Right before Akademy there’s also the Qt Contributor Summit in Würzburg.

I have been asked to give a short presentation on experiences with migrating to Qt 6 and maintaining software on top of Qt 6, both from the KDE and from my work perspective. Currently that’s planned for the morning talks session on Thursday.

Kongress

And as usual there’s also code to finish before Akademy. This time the important one has been the venue map integration for Kongress. Not only does this have to be ready for my talk about it, it’s also meant to be used at Akademy.

Time-dependent maps

A big part still missing in the previous post was support for time-dependent content. While conceptually clear (OSM has time-conditional tags and we have an parser/evaluator for the opening hours expressions used in those) I was still hoping we might get away without that.

And we would now (again), but there was an intermediate state of the Akademy schedule until two days ago where “Room 1” referred to a different physical room on Saturday and Sunday than during the rest of the week. While that might sound confusing, this is actually not unusual, so properly supporting all that makes sense anyway.

With the rooms now having distinct names again I can’t easily demonstrate the full power of time-dependent content anymore, but you’ll still notice that the venue map looks different depending on which day you open it (by opening it from events on different dates, or by waiting a week).

Android dark mode support

To look good for the occasion we now also have proper Android dark mode support in KDE Frameworks, thanks to Julius’s recent work on icon recoloring, and the necessary changes in Kongress will be part of 24.08.1.

Kongress on Android in dark mode. xCal support

With September being packed with conferences, another problem has become pressing for Kongress as well: Pretalx (a widely used open source conference management software) apparently removed access to the schedule in iCal format, something Kongress relies on. Akademy isn’t affected due to using a different system, but SotM or the Matrix Conference are.

As a solution we could roll out quickly I added basic xCal read support to KF::CalendarCore, a format Pretalx still supports. This has been integrated yesterday and should make it in the September update of KDE Frameworks.

Itinerary

Since conferences involve travel for most people, Itinerary can’t go without attention either.

For making coordination of train trips easier, the “Share Journey” links you’ll get from Deutsche Bahn’s website can now be directly imported.

Akademy and all its sub-events are machine-readable on their corresponding websites as usual, and thus can also be imported by pasting/opening/dropping the corresponding URLs in(to) Itinerary.

The day trip shows some limitations though, we don’t have a good way to model that yet. It’s now imported as an event, but ideally it would be two bus trips, so you have the most crucial information (places and times of the bus departures) included.

Itinerary supports bus trips, but we have a few specialties here. The departure location of the return trip is still unknown (expecting that to be where we get dropped off), so this needs to be editable. For bus trips however Itinerary doesn’t allow arbitrary changes when editing, so you can’t accidentally end up with something that can no longer be matched against realtime data. That’s however irrelevant for chartered buses, those don’t show up on any schedule anyway.

So that bus trip is actually less of a bus trip in the public transport sense, but more an individual transport trip (ie. similar to taking your own bike or car). And that’s something we don’t support in Itinerary at all yet. If you have input on how that should look like, I’d be happy to hear that in the Itinerary Matrix channel!

Hoping nobody gets lost due to an Itinerary bug on the way and very much looking forward to seeing many of you next week in Würzburg!

Categories: FLOSS Project Planets

Carl Trachte: Scaleable Vector Graphics (svg) - Decomposing and Scaling Elements for Blogger

Planet Python - Sat, 2024-08-31 02:35

Last post I lamented the failure of any the svg graphs I had for DAG Hamilton workflows to render in Blogger. I set out to rectify this and have had some initial success.

My first attempt to get svg to show up in Blogger has the DAG Hamilton logo as its subject. My approach was to bring the svg elements down to the most basic primitives I could manage, then scale and translate their individual coordinates to bring them into the view.

Fortunately, I was able to find an example from someone who had successfully rendered svg in Blogger. The subject blog post is eleven years old. It appears svg never totally caught on for some platforms. Nonetheless, I used this as a template, and, after some initial success, managed to render the Hamilton logo.

The post will step through the individual path components of the Hamilton logo (7) and show the code used to transform the coordinates to make the svg elements render at an appropriate location and size. The individual path elements that make up the logo are a large number of 3 coordinate bezier curves. The manner in which the curves listed is very format specific to the platform that created them. Unfortunately, I cannot recall which online png to svg converter I used to create the svg. Portable Inkscape kept crashing on the large png file, so I brute forced the issue by using an online converter.

The first element of the logo is basically a purple background for the whole logo. A gap or divit can be seen just left of center on the upper part. I'll cover the manual "fixing" of this further on.



Second path (orange)



Third path (deep purple)





Fourth path (yellow top point)

                                        

Fifth path (light orangey left leg were the star to face out from the screen)

                                      

Sixth path (medium purple little triangle in the center)

                                      

Seventh path (light orangey little triangle to the right of center)

                                   


Fixing that niggling divit on the top half of the logo (the thin subvertical line).


                                     


Final product. This is the one svg inline drawing I was able to show (content size limitations of Blogger?)


The full logo refuses to render (although I swear it did before). Well, at least there is an svg element showing up on the page (a purple star). <sigh> Another png . . .

                                   


The code. I'm not strong on HTML, but it was necessary to edit this post inline. As part of that exercise, I included the post outline generation as part of the script.



# python 3.12


# blog_post_make_outline.py


"""

Attempt to scale, translate, and inline svg

elements for display in Blogger.

"""


import re


import pprint


import sys


import copy


import xml.etree.ElementTree as ET


# REGEX patterns.


PATHPAT = r'[<]path[ ]d[=]["]'


MPAT = r'M([-]*[0-9]+[.]*[0-9]*)[ ]([-]*[0-9]+[.]*[0-9]*)[ ]'


          # first bezier curve coord

BEZPAT = (r'C([-]*[0-9]+[.]*[0-9]*)[ ]([-]*[0-9]+[.]*[0-9]*)[ ]'

          # second bezier curve coord

          r'([-]*[0-9]+[.]*[0-9]*)[ ]([-]*[0-9]+[.]*[0-9]*)[ ]'

          # third bezier curve coord

          r'([-]*[0-9]+[.]*[0-9]*)[ ]([-]*[0-9]+[.]*[0-9]*)[ ]')


ZPAT = r'Z[ ]'


FILLPAT = (r'["] fill[=]["]([#][A-F0-9][A-F0-9][A-F0-9]'

           r'[A-F0-9][A-F0-9][A-F0-9])["][ ]')


#                 transform="translate(4756.96875,109.12890625)" 

#                 transform="translate(5619,4112)"/>

TRANSFORMPAT = (r'transform[=]["]translate[(]'

                r'([-]*[0-9]+[.]*[0-9]*)[,]([-]*[0-9]+[.]*[0-9]*)[)]["]')


# Output formats/constants.


BEZFMT = ('C{0:.7f} {1:.7f} '

          '{2:.7f} {3:.7f} '

          '{4:.7f} {5:.7f} ')


PATHFMT_OPEN = '<path d="'


PATHFMT_1 = 'M{mstartx:.5f} {mstarty:.5f} {path:s} Z '


PATHFMT_2 = '" fill="{fill:s}" transform="translate({translatex:.7f},{translatey:.7f})"'


PATHFMT_CLOSE = ' />'


SVG_TAG_OPEN = ('<svg xmlns="http://www.w3.org/2000/svg" '

                'xmlns:xlink="http://www.w3.org/1999/xlink" '

                "width='500px' height='500px'>")


SVG_TAG_CLOSE = '</svg>'


def parse_path(pathstring):

    """

    Capture path elements in a dictionary.


    pathstring is the svg string for the path (one line).


    For a path comprised entirely of bezier curves

    in the format (all one line):


    <ns0:path d="M0 0 C2.54601018 1.57157072 5.09846344 3.13131386 7.65625 4.68359375 C39.179 . . .  0 0 Z M-690.96875 4007.87109375 C-707.702 . . .  Z " fill="#C3368C" transform="translate(4756.96875,109.12890625)" />


    Returns dictionary.

    """

    retval = {}

    patpath = re.compile(PATHPAT)

    match = patpath.match(pathstring)

    startindex = match.span()[1]

    mpat = re.compile(MPAT)

    # MPAT

    match = mpat.match(pathstring[startindex:])

    mpatgroups = match.groups()

    retval['mpatgroups'] = []

    retval['mpatgroups'].append(mpatgroups)

    startindex += match.span()[1]

    bezpat = re.compile(BEZPAT)

    zpat = re.compile(ZPAT)

    retval['paths'] = []

    while match:

        pathpoints = []

        # BEZPAT

        match = bezpat.match(pathstring[startindex:])

        while match:

            # Sentinel.

            if not match:

                continue

            pathpoints.append(match.groups())

            startindex += match.span()[1]

            match = bezpat.match(pathstring[startindex:])

        retval['paths'].append(pathpoints)

        # ZPAT

        match = zpat.match(pathstring[startindex:])

        startindex += match.span()[1]

        # Then look for MPAT

        # MPAT

        match = mpat.match(pathstring[startindex:])

        # If MPAT not there, work on color and transform.

        if not match:

            continue

        startindex += match.span()[1]

        retval['mpatgroups'].append(mpatgroups)

    fillpat = re.compile(FILLPAT)

    match = fillpat.match(pathstring[startindex:])

    startindex += match.span()[1]

    print('adding fill . . .')

    fill = match.groups()[0]

    retval['fill'] = fill

    transformpat = re.compile(TRANSFORMPAT)

    match = transformpat.match(pathstring[startindex:])

    transform = match.groups()

    print('adding transform . . .')

    retval['transform'] = transform

    return retval


def parse_all_paths(svgfilepath):

    """

    Finds and parses all svg paths

    within an svg file (very format

    specific - bezier curves only).


    Returns list of dictionaries, one

    for each path line of the svg file.

    """

    # Do all paths in Hamilton logo.

    # Make list of dictionaries.

    patpath = re.compile(PATHPAT)

    with open(svgfilepath, 'r') as f:

        paths = []

        # Line one.

        next(f)

        # Line two.

        next(f)

        for linex in f:

            print(PATHPAT)

            print(linex[:30])

            match = patpath.match(linex)

            if not match:

                break

            paths.append(parse_path(linex))

    return paths


def work_paths(paths, fcn):

    """

    Apply an operation to all coordinates in

    the bezier curve paths represented in

    paths.


    Also covers translate and fill.


    paths is a list of dictionaries. Each

    dictionary represents one line of an

    svg file with a path made up of 

    bezier curves.

    """

    # Return value.

    newpaths = []

    # M - start of path segment.

    for pthx in paths:

        newmpatgroups = []

        for coordsx in pthx['mpatgroups']:

            newmpatgroups.append([fcn(x) for x in coordsx])

        newpaths.append({'mpatgroups':newmpatgroups})

    # to Z - end of path segment.

    # List of path dictionaries (paths).

    for pthx, newpath in zip(paths, newpaths):

        newpath['paths'] = []

        # Each M to Z path segment.

        for path in pthx['paths']:

            curvegroup = []

            for curve in path:

                newcurve = [fcn(x) for x in curve]

                curvegroup.append(newcurve)

            newpath['paths'].append(curvegroup)

    # transform and fill.

    for pthx, newpath in zip(paths, newpaths):

        newpath['transform'] = [fcn(x) for x in pthx['transform']]

        newpath['fill'] = pthx['fill']

    return newpaths


def translate_paths(paths, translation):

    """

    From a two tuple of x, y translation,

    adjust dictionary values for x, y 

    translation in each path in path

    list accordingly.


    Returns new dictionary

    """

    # TRANSLATE

    # ['mpatgroups', 'paths', 'fill', 'translate']

    translated_paths = copy.deepcopy(paths)

    for pathx in translated_paths:

        pathx['transform'][0] += translation[0]

        pathx['transform'][1] += translation[1]

    return translated_paths


def get_path_strings(paths):

    """

    From a list of path dictionaries, 

    builds one line strings for insertion

    into svg file.


    Returns list

    """

    pathdict_2 = {'fill':None,

                'translatex':None,

                'translatey':None}

    path_segment_dict = {'mstartx':None,

                         'mstarty':None,

                         'path':None}

    pathstrings = []

    for pathx in paths:

        # Copy and initialize fill/translate dictionary.

        fill_translate = copy.deepcopy(pathdict_2)

        fill_translate['fill'] = pathx['fill']

        fill_translate['translatex'] = pathx['transform'][0]

        fill_translate['translatey'] = pathx['transform'][1]

        # Zip together M and path segments.

        path_segs = zip(pathx['mpatgroups'], pathx['paths'])

        # For each path segment.

        path_strings = []

        for M, path_seg in path_segs:

            seg_dict = copy.deepcopy(path_segment_dict)

            seg_dict['mstartx'] = M[0]

            seg_dict['mstarty'] = M[1]

            # Build path segment string.

            path_seg_strings = [BEZFMT.format(*coords) for coords in path_seg]

            path = ''.join(path_seg_strings)

            seg_dict['path'] = path

            # Make final segment string with M (PATHFMT_1)

            path_with_M = PATHFMT_1.format(**seg_dict)

            path_strings.append(path_with_M)

        path_all_together = ''.join(path_strings)

        # Tack on fill/translate at end and beginning with d path flag.

        # Add to pathstrings.

        pathstrings.append(PATHFMT_OPEN  +

                           path_all_together +

                           PATHFMT_2.format(**fill_translate) +

                           PATHFMT_CLOSE)

    return pathstrings  


paths = parse_all_paths('hamilton_logo_large.svg')

print('len(paths) = {0:d}'.format(len(paths)))

pprint.pprint([x for x in paths[0]])


paths = work_paths(paths, float)


scale = 0.035

scaleit = lambda x: scale * x


paths = work_paths(paths, scaleit)


# pprint.pprint(paths[0]['paths'][0])


paths = translate_paths(paths, (125, 0))


pathstrings = get_path_strings(paths)


# with open('test_paths.txt', 'w') as f:

#     for pathx in pathstrings:

#         print(pathx, file=f)

#         print('\n\n', file=f)


GAP_FIX = "<polygon points='265 90.25, 245 143.75, 268 144.1, 295 90.25' style='fill: black;' />"

GAP_FIX_PROPER_COLOR = "<polygon points='265 90.25, 245 143.75, 268 144.1, 295 90.25' style='fill: #C3368C;' />"


TEXT = '<p>{0:s}</p>'


CODE = """

<p>&nbsp;</p><pre style="background: rgb(238, 238, 238); border-bottom-color: initial; border-bottom-style: initial; border-image: initial; border-left-color: initial; border-left-style: initial; border-radius: 10px; border-right-color: initial; border-right-style: initial; border-top-color: rgb(221, 221, 221); border-top-style: solid; border-width: 5px 0px 0px; color: #444444; font-family: &quot;Courier New&quot;, Courier, monospace; font-stretch: inherit; font-variant-east-asian: inherit; font-variant-numeric: inherit; line-height: inherit; margin-bottom: 1.5em; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: 12px; vertical-align: baseline;"><span style="font-size: 13px;">{0:s}</span></pre>

"""


textlist = ['blah blah blah',

            'blah blah blah again',

            'blah blah blah a third time',

            'blah blah blah a fourth time',

            'blah blah blah a fifth time',

            'blah blah blah a sixth time',

            'blah blah blah a seventh time',

            'fix divit',

            'final product']


fixed_divit = copy.deepcopy(pathstrings)

fixed_divit.insert(1, GAP_FIX_PROPER_COLOR)


svglist = [pathstrings[0:1],

           pathstrings[0:2],

           pathstrings[0:3],

           pathstrings[0:4],

           pathstrings[0:5],

           pathstrings[0:6],

           pathstrings[0:],

           pathstrings[0:] + [GAP_FIX],

           fixed_divit]


with open('blogpost.html', 'w') as f:

    for blah, svgels in zip(textlist, svglist):

        print(TEXT.format(blah), file=f)

        print(SVG_TAG_OPEN, file=f)

        for svgelement in svgels:

            print(svgelement, file=f)

        print(SVG_TAG_CLOSE, file=f) 


    print(TEXT.format('More blah about code'), file=f)


    print(CODE.format('>>> import this'), file=f)


print('Done')

Notes:

1) I had had good intentions of including a code box (the CODE string constant in the Python code) and I hit a bit of a wall. Not only is my code a mixed bag (this blog was always intended as a learning experience and a place for trying things out), it doesn't look good. We deal.

Which brings me to the point: you hear titles like front end developer, designer, website marketer etc. and think, "Well, it's kind of like art, kind of like coding . . . sort of creative." I now know, it's coding and it's thinking and it's grinding. All respect.

2) Steven Lott recently published a book that I bought (pdf). I have found it helpful. It's very pragmatic. I'm only about 15% of the way into it, but his treatment of regular expressions as just another tool not be scared of made me less tentative in my REGEX use (even if my REGEXes are far from elegant). Group capture with parens was really helpful for this exercise.

3) Our chief geology database administrator commented that the colors of the DAG Hamilton logo are those of the Spanish shawl nudibranch. There is a resemblance.

Photo courtesy of iNaturalist.



Thanks for stopping by.

Categories: FLOSS Project Planets

Pages