tilleraj / LightWeightMTGProxy

Generating lightweight proxies for playtesting new Magic The Gathering decks at home
MIT License
2 stars 1 forks source link

Paneswalkers: #5

Open tilleraj opened 4 years ago

tilleraj commented 4 years ago
tilleraj commented 4 years ago

The default layout seems to work just fine. Loyalty has been added with it's own position and is placed where P/T are.

tilleraj commented 4 years ago

Overflow Handling

While not limited to Planeswalkers, this is where I first encountered it. The issue is that card text is not bounded by the text frame and can overflow from its designated area. Text that is too long needs to be sized to fit. Below I've provided examples of the issue and included two attempts at fixing it.

A more mathematically and practically elegant solution is desired.

Zero Overflow Handling

Below we see Chandra, Pyromaster and Jace, Architect of Thought's last lines touching the outside of the text box. Screen Shot 2020-06-01 at 11 39 29 AM Here we see all cards overflowing, Greater Morphling being the worst offender, bleeding into the card below it. Screen Shot 2020-06-01 at 11 40 22 AM

Straight Percentage

(There are some obvious mathematical oversights with this method looking at it now, but here it is nonetheless.)

In these examples, Line Height, Font Size, and Paragraph Spacing are all reduced by the percentage of characters over the limit.

(totalLetters+(numNewLines*newLineCharWeight))/charCap-1 So with a charCap of 425 and a newLineCharWeight of 50, you get a reasonable

Chandra, Pyromaster - {2}{R}{R} (Legendary Planeswalker — Chandra)
355 letters + 2 new lines was too big to fit.
 * lineHeight reduced 7.06% from 2.2 to 2.0447058823529414
 * fontSize reduced 7.06% from 2.2 to 2.0447058823529414
 * paragraphSpacing reduced 7.06% from 0.5 to 0.4647058823529412
355+2*50 = 455
Jace, Architect of Thought - {2}{U}{U} (Legendary Planeswalker — Jace)
370 letters + 2 new lines was too big to fit.
 * lineHeight reduced 10.59% from 2.2 to 1.9670588235294117
 * fontSize reduced 10.59% from 2.2 to 1.9670588235294117
 * paragraphSpacing reduced 10.59% from 0.5 to 0.44705882352941173
370+2*50 = 470

Note: Jace is reduced more than necessary Screen Shot 2020-06-01 at 12 04 23 PM

This method is over-zealous with more verbose cards. Master of The Hunt looks alright:

Master of the Hunt - {2}{G}{G} (Creature — Human)
487 letters + 0 new lines was too big to fit.
 * lineHeight reduced 14.59% from 2.2 to 1.8790588235294117
[...]487+0*50 = 487

But cards with multiple paragraphs are far too reduced

Bureaucracy - {3}{U}{U} (Enchantment)
617 letters + 1 new lines was too big to fit.
 * lineHeight reduced 56.94% from 2.2 to 0.9472941176470591
[...]617+1*50 = 667
Greater Morphling - {6}{U}{U} (Creature — Shapeshifter)
510 letters + 6 new lines was too big to fit.
 * lineHeight reduced 90.59% from 2.2 to 0.20705882352941202
[...]510+6*50 = 810

Screen Shot 2020-06-01 at 11 43 54 AM

Terrible, Horrible, No Good, Very Bad Day Math

I got a bit creative and manipulated straight percentage until it gave me something resembling the desired results. I also tried tweaking newLineCharWeight and charCap to little success and the values I started with worked the best. I tried reducing values based on how much of the charCap was contributed to by new lines vs text, adjusting paragraphSpacing first and more heavily for new line heavy text, but got mixed results. Below are the flawed but best results I've gotten so far.

pctOverCap = (totalLetters+(numNewLines*newLineCharWeight))/charCap-1
pctOverCap = math.sqrt(pctOverCap*100)/30

Chandra and Jace are slightly smaller than straight-percentage. Chandra 8.86% from 7.06 and Jace 10.85% from 10.59%

Chandra, Pyromaster - {2}{R}{R} (Legendary Planeswalker — Chandra)
355 letters + 2 new lines was too big to fit.
 * lineHeight reduced 8.86% from 2.2 to 2.0051647251811793
[...]355+2*50 = 455
Jace, Architect of Thought - {2}{U}{U} (Legendary Planeswalker — Jace)
370 letters + 2 new lines was too big to fit.
 * lineHeight reduced 10.85% from 2.2 to 1.9613764963994784
[...]370+2*50 = 470

Screen Shot 2020-06-01 at 11 41 49 AM The biggest change, however, can be seen with the more verbose cards. Master of The Hunt is reduced 12.73% vs 14.59% in straight percentage, Bureaucracy 25.15% vs 56.94%, and Greater Morphling 31.73% vs 90.59%

Master of the Hunt - {2}{G}{G} (Creature — Human)
487 letters + 0 new lines was too big to fit.
 * lineHeight reduced 12.73% from 2.2 to 1.919906644881212
[...]487+0*50 = 487
Bureaucracy - {3}{U}{U} (Enchantment)
617 letters + 1 new lines was too big to fit.
 * lineHeight reduced 25.15% from 2.2 to 1.6466312319401715
[...]617+1*50 = 667
Greater Morphling - {6}{U}{U} (Creature — Shapeshifter)
510 letters + 6 new lines was too big to fit.
 * lineHeight reduced 31.73% from 2.2 to 1.5020290846368953
[...]510+6*50 = 810

Screen Shot 2020-06-01 at 11 41 22 AM

tilleraj commented 4 years ago

Not pictured above

While not pictured above to save space, Narset Transcendent specifically has been a pain to deal with.

Chandra, Pyromaster 355+250 = 455 Jace, Architect of Thought 370+250 = 470 Narset Transcendent 330+2*50 = 430

tilleraj commented 4 years ago

Ideal Solution?

Here is a plot of plot y=1-1e^(-0.0035x) and y=sqrt(x)/30 from x=-10 to x=800 y=-0.25 to y=1.25 taken from [WolframAlpha](https://www.wolframalpha.com/input/?i=plot+y%3D1-1e%5E%28-0.0035x%29%2C+y%3Dsqrt%28x%29%2F30+from+x%3D-10+to+x%3D800+y%3D-0.25+to+y%3D1.25) exp_vs_sqrt The early reduction from sqrt is valuable, but being asymptotic is nice. There is a practical maximum length of text WotC is willing to print. Bureaucracy is a joke card meant to be as long as possible. Coming in at 644 (694 with newlines) or 617 with alnum check, it can be considered the longest possible, eliminating the need for an asymptote as long as it is handled correctly.

Maintaining a charCap of 425 and using 694 as our max, we only need to consider up to 269 extra characters

Something like y=0.65-0.58*e^(-0.0065x) may be the best?

peteigel commented 4 years ago

I’m reading this on my phone now, so I may be missing something. My ideal approach would be to something like

font_size = DEFAULT_FONT_SIZE
layoutText()

while (textOverflowsContainer):
  font_size = font_size - SIZE_INCREMENT
  layoutText()
tilleraj commented 4 years ago

Definitely more straightforward and simple.

I guess the easiest way is measure the height of the text box and check that currentOffset is less than the box height and if not, throw an exception then wrap the showWrappedText call in a try/except, with re-running it with smaller lineHeight on except?

 # Draw cardText
 ctx.set_font_size(layout.cardTextH)
 showWrappedText(ctx, card.cardText,
    top=layout.cardTextBL[1],
    left=layout.cardTextBL[0],
    right=layout.cardTextBL[0] + layout.cardTextW,
    lineHeight=layout.cardTextH
    )
peteigel commented 4 years ago

I think the best way to handle this is to draw the text to a fake surface and just measure the height. Something vaguely like

def fitText(text):
  ctx = makeFakeContext()
  lineHeight = MAX_LINE_HEIGHT

  while True:
    textHeight = showWrappedText(
       ctx, text, 
        top=0, 
        left=0, 
        right=layout.cardTextW, 
        lineHeight=lineHeight)

    if textHeight <= layout.cardTextH:
      return lineHeight

    lineHeight = lineHeight - LINE_HEIGHT_INCREMENT

Where showWrappedText has been modified to return the final offset. Then when you draw, you just call fitText() first ad set the text size. That way you don't to clear and redo any work on the real surface.