Im trying to sign an image with GD.
I have a line of text, which may look like this: "This is a very long line of text"
If I use 60th font, it wont fit in my image, so I need some how do wordwrap
Now I'm using loop that is doing word wrap limited by number of symbols and adding text to image line by line..
$words = explode(" ", $text);
if (strlen($words[0]) > 20)
$output = substr($words[0], 0, 20);
else {
$output = array_shift($words);
while (strlen($output . " " . $words[0]) <= 20) {
$output .= " " . array_shift($words);
}
}
$text = $str2 = substr($text, strlen($output));
list($left,, $right) = imageftbbox($font_sizet, 0, $font_path, $output);
$width = $right - $left;
$leftx = (740 - $width) / 2;
$topy = (560 + $i * 30);
imagettftext($img, $font_sizet, 0, $leftx, $topy, $white, $font_path, $output);
The problem with this script is that if the first letters are going to be "WWWWWWWWWWW", the image will fit only 10 symbols... and if first letters are going to be "llllllllllll" the text will look short.
The width of text I cant determine by using this line:
list($left,, $right) = imageftbbox( $font_sizet, 0, $font_path, $output);
$width = $right - $left;
<- i think that somehow I can use this $width in my
loop to compare with maxlength
How can I change limiting by number of symbols to limiting by number of pixels (lets say, I want the line to be no longer than 300px)?
Related
I made a code using pysimplegui. it basically shows some images from a database based on a scanned number. it works but sometimes it could be useful to be able to increase the size of the image + it would make my user interface a bit more interactive
i want to have the possibility to either:
when i fly over the image with the mouse, i want the image to increase in size
have the possibility to clic on the image and have a pop-up of the image showing up (in a bigger size)
i am not sure on how to interact with a sg.image()
Below you will find a trunkated part of my code where i show my way of getting the image to show up.
layout = [
[
sg.Text("Numéro de boîte"),
sg.Input(size=(25, 1), key="-FILE-"),
sg.Button("Load Image"),
sg.Button("Update DATA"),
sg.Text("<- useless text ")
],
[sg.Text("Indicateur au max" , size = (120, 1),font = ("Arial", 18), justification = "center")],
[sg.Image(key="-ALV1-"),sg.Image(key="-ALV2-"), sg.Image(key="-ALV3-"), sg.Image(key="-ALV4-"), sg.Image(key="-ALV5-")],
[sg.Image(key="-ALV6-"),sg.Image(key="-ALV7-"), sg.Image(key="-ALV8-"), sg.Image(key="-ALV9-"), sg.Image(key="-ALV10-")],
[sg.Text("_" * 350, size = (120, 1), justification = "center")],
[sg.Text("Indicateur au milieu" , size = (120, 1),font = ("Arial", 18), justification = "center")],
[sg.Image(key="-ALV11-"),sg.Image(key="-ALV12-"), sg.Image(key="-ALV13-"), sg.Image(key="-ALV14-"), sg.Image(key="-ALV15-")],
[sg.Image(key="-ALV16-"),sg.Image(key="-ALV17-"), sg.Image(key="-ALV18-"), sg.Image(key="-ALV19-"), sg.Image(key="-ALV20-")],
[sg.Text("↓↓↓ ↓↓↓" , size = (120, 1),font = ("Arial", 18), justification = "center")],
]
ImageAlv1 = Image.open(PathAlv1)
ImageAlv1.thumbnail((250, 250))
bio1 = io.BytesIO()
ImageAlv1.save(bio1, format="PNG")
window["-ALV1-"].update(data=bio1.getvalue())
Using bind method for events, like
"<Enter>", the user moved the mouse pointer into a visible part of an element.
"<Double-1>", specifies two click events happening close together in time.
Using PIL.Image to resize image and io.BytesIO as buffer.
import base64
from io import BytesIO
from PIL import Image
import PySimpleGUI as sg
def resize(image, size=(256, 256)):
imgdata = base64.b64decode(image)
im = Image.open(BytesIO(imgdata))
width, height = size
w, h = im.size
scale = min(width/w, height/h)
new_size = (int(w*scale+0.5), int(h*scale+0.5))
new_im = im.resize(new_size, resample=Image.LANCZOS)
buffer = BytesIO()
new_im.save(buffer, format="PNG")
return buffer.getvalue()
sg.theme('DarkBlue3')
number = 4
column_layout, line = [], []
limit = len(sg.EMOJI_BASE64_HAPPY_LIST) - 1
for i, image in enumerate(sg.EMOJI_BASE64_HAPPY_LIST):
line.append(sg.Image(data=image, size=(64, 64), pad=(1, 1), background_color='#10C000', expand_y=True, key=f'IMAGE {i}'))
if i % number == number-1 or i == limit:
column_layout.append(line)
line = []
layout = [
[sg.Image(size=(256, 256), pad=(0, 0), expand_x=True, background_color='green', key='-IMAGE-'),
sg.Column(column_layout, expand_y=True, pad=(0, 0))],
]
window = sg.Window("Title", layout, margins=(0, 0), finalize=True)
for i in range(limit+1):
window[f'IMAGE {i}'].bind("<Enter>", "") # Binding for Mouse enter sg.Image
#window[f'IMAGE {i}'].bind("<Double-1>", "") # Binding for Mouse double click on sg.Image
element = window['-IMAGE-']
now = None
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
elif event.startswith("IMAGE"):
index = int(event.split()[-1])
if index != now:
element.update(data=resize(sg.EMOJI_BASE64_HAPPY_LIST[index]))
now = index
window.close()
I have a powershell function that I need to be able to change. The function centers text in the terminal however, I need to be able to output multiple colors for text on a single line. If I do -NoNewLine and do more Write-host to change the color... then it still calculates the width of the terminal and still adds as much padding as it would without me adding -NoNewLine. Essentially I want my text centered and I want to be able to use multiple colors. With what I have I can only do 1 color per line.
function WriteCentered([String] $text, $color = $null)
{
$width = [int](Get-Host).UI.RawUI.BufferSize.Width
$twidth = [int]$text.Length
$offset = ($width / 2) - ($twidth / 2)
$newText = $text.PadLeft($offset + $twidth)
if($color)
{
Write-Host $newText -ForegroundColor $color
}
else
{
Write-Host $newText
}
}
I have added more IF conditions, I have changed my padding calculations, I am having trouble with getting it just right.
The PowerShell-Module PSWriteColor already does a good job in outputting multiple colors on a single line. Either you download it from GitHub directly and import it with Import-Module <PATH-TO>\PSWriteColor.psd1 or you install it from the PowerShell Gallery directly with Install-Module -Name PSWriteColor.
The syntax in short is Write-Color -Text "GreenText","RedText","BlueText" -Color Green,Red,Blue. So we need to prepend the [String[]]$Text argument with a string containing the necessary whitespace in order to center the message on the screen and prepend a color to the [ConsoleColor[]]$Color argument accordingly.
Here's a little helper function for centering.
#Requires -Modules #{ ModuleName="PSWriteColor"; ModuleVersion="0.8.5" }
function WriteColor-Centered {
param(
[Parameter(Mandatory=$true)][string[]]$Text,
[Parameter(Mandatory=$true)][ConsoleColor[]]$Color
)
$messageLength = 0
$Text | ForEach-Object { $messageLength += $_.Length }
[String[]] $centeredText = "{0}" -f (' ' * (([Math]::Max(0, $Host.UI.RawUI.BufferSize.Width / 2) - [Math]::Floor($messageLength / 2))))
$centeredText += $Text
[ConsoleColor[]]$OutColor = #([ConsoleColor]::White)
$OutColor += $Color
Write-Color -Text $centeredText -Color $OutColor
# Alt.: use WriteColor-Core, see below
# WriteColor-Core -Text $centeredText -Color $OutColor
}
I copied the whitespace calculation from this stackoverflow answer.
EDIT: I was being asked if it's possible to make this work without importing the module. To be honest I feel a little dirty now because I went into source code of a well-written module stripped all the functionality and error handling from it and pasted it here.
Well anyways - if you replace the invocation of Write-Color in the wrapper function above and invoke the following WriteColor-Core instead you can dispense with loading the PSWriteColor module.
function WriteColor-Core {
param(
[Parameter(Mandatory=$true)][string[]]$Text,
[Parameter(Mandatory=$true)][ConsoleColor[]]$Color
)
# Fallback defaults if one of the values isn't set
$LastForegroundColor = [console]::ForegroundColor
# The real deal coloring
for ($i = 0; $i -lt $Text.Count; $i++) {
$CurrentFGColor = if ($Color[$i]) { $Color[$i] } else { $LastForegroundColor }
$WriteParams = #{
NoNewLine = $true
ForegroundColor = $CurrentFGColor
}
Write-Host $Text[$i] #WriteParams
# Store last color set, in case next iteration doesn't have a set color
$LastForegroundColor = $CurrentFGColor
}
Write-Host
}
I'm working on a program that creates several pdf docs and puts different text in the same location in them.
Text should be placed in a particular area and if it doesn't fit it in width it should wrap. It also has a custom font and may be differently aligned in that area. It should be Vertically aligned to Top because when the area is laid out for three lines and I has only one, it should appear on the top. Finally, I need to preserve leading on the level of font-size.
It is important to be precise in text positioning (e.g. I need an upper left corner of "H" from "Hello world" to appear definitely at 0, 100).
Now, I'm using
canvas.showTextAligned(paragraph, 0, 300,
TextAlignment.valueOf(alignment),
VerticalAlignment.TOP);
However, when I try to implement it with different fonts it has a various offset from desired y = 300. Moreover, offset differ from font to font. For Helvetica (everywhere 50 fontSize is used) offset is about 13 px, for Oswald about 17 px and for SedgwickAveDisplay it is massive 90 px.
I added borders to paragraph for debugging purpose and things become more strange.
Helvetica:
SedgwickAveDisplay:
The full snippet of my code to create pdf is below:
public byte[] createBadgeInMemory(int i) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
PdfDocument newPdf = new PdfDocument(new PdfWriter(out));
srcPdf.copyPagesTo(1,1,newPdf);
PdfPage page = newPdf.getFirstPage();
PdfCanvas pdfCanvas = new PdfCanvas(page);
Canvas canvas = new Canvas(pdfCanvas, newPdf, pageSize);
File defaultFont = new File("src/main/resources/fonts/Helvetica.otf");
PdfFont font = PdfFontFactory
.createFont(fontPath == null ? defaultFont.getAbsolutePath() : fontPath,
PdfEncodings.IDENTITY_H, true);
String value = "Example word";
Paragraph paragraph = new Paragraph(value);
float textWidth = font.getWidth("Example", 50);
paragraph.setWidth(textWidth);
switch (alignment) {
case("CENTER"):
textWidth /= 2;
break;
case("RIGHT"):
break;
default:
textWidth = 0;
break;
}
paragraph.setFont(font)
.setFontSize(fontSize)
.setFixedLeading(fontSize)
.setFontColor(new DeviceRgb(red, green, blue))
.setMargin(0)
.setPadding(0);
paragraph.setBorderTop(new DashedBorder(Color.BLACK, 0.5f))
.setBorderBottom(new DashedBorder(Color.BLACK, 0.5f))
.setBorderRight(new DashedBorder(Color.BLACK, 0.5f));
paragraph.setHyphenation(new HyphenationConfig(0,
"Example".length()));
canvas.showTextAligned(paragraph,
0 + textWidth,
300,
TextAlignment.valueOf(alignment),
VerticalAlignment.TOP);
newPdf.close();
return out.toByteArray();
}
I also tried variant from here, but for some reason text inside rectangle cuts out at some point (for instance, if I have area width 100px and text snippet I put in that I know occupies exactly 100 px (with the help of font.getWidth(value)), I have my text cut at nearly 80 px).
I also haven't found a way to align text inside a rectangle.
This is the result with Rectangle. A solid border is Rectangle border. As you can see it cuts letter "t" in "Redundant". It also should contain "word" on the second line, but it doesn't.
I copied code from an example.
I need your help. What am I doing wrong or may be there is another way to layout text in particular area with alignment and font?
Thank you in advance.
Update 21.09.17
Also tried variant from this question with SedgwickAveDisplay:
paragraph.setHorizontalAlignment(HorizontalAlignment.LEFT);
paragraph.setVerticalAlignment(VerticalAlignment.TOP);
paragraph.setFixedPosition( 0, 300 - textHeight, "Example".length());
doc.add(paragraph);
The result is the same as on the second screenshot.
This is a font-specific problem. iText guesses information about font glyphs, namely the bboxes incorrectly.
There is a quick and dirty method to adjust this behavior. You can create a custom renderer for text and adjust the calculated positions in it. An example of such a class would be as follows:
class CustomTextRenderer extends TextRenderer {
private CustomTextRenderer(Text text) {
super(text);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
LayoutResult result = super.layout(layoutContext);
Rectangle oldBbox = this.occupiedArea.getBBox().clone();
// you can also make the height more than font size or less if needed
this.occupiedArea.setBBox(oldBbox.moveUp(oldBbox.getHeight() - fontSize).setHeight(fontSize));
yLineOffset = fontSize * 0.8f; // here you config the proportion of the ascender
return result;
}
#Override
public IRenderer getNextRenderer() {
return new CustomTextRenderer((Text) modelElement);
}
}
In order that new rendering logic to be applied, you have to use it in the following way:
Text text = new Text(value);
text.setNextRenderer(new CustomTextRenderer(text));
Paragraph paragraph = new Paragraph(text);
Please note that you have to be very careful with this kind of low-level layouting, be aware of you are doing and use this as seldom as possible.
Finally, I created a variant that worked for me.
pdfCanvas.beginText()
.setFontAndSize(font, fontSize)
.setLeading(fontSize)
.moveText(0, 300);
numberOfLines = 0;
sumOfShifts = 0;
float maxWidth = computeStringWidth("Exaxple");
String[] words = value.split("\\s");
StringBuilder line = new StringBuilder();
line.append(words[0]);
float spaceWidth = computeStringWidth(" ") ;
float lineWidth;
for (int index = 1; index < words.length; index++) {
String word = words[index];
float wordWidth = computeStringWidth(word) ;
lineWidth = computeStringWidth(line.toString()) ;
if (lineWidth + spaceWidth + wordWidth <= maxWidth) {
line.append(" ").append(word);
} else {
showTextAligned(alignment, pdfCanvas, line.toString(), lineWidth, maxWidth);
line.delete(0, line.length());
line.append(word);
}
}
if(line.length() != 0) {
lineWidth = computeStringWidth(line.toString()) ;
showTextAligned(alignment, pdfCanvas, line.toString(), lineWidth, maxWidth);
}
pdfCanvas.endText();
As computeStringWidth(String str) I used
Toolkit.getToolkit().getFontLoader().computeStringWidth(String str, Font font);
from import com.sun.javafx.tk.Toolkit with Font from javafx.scene.text.Font. I've chosen it because I use it in other parts of my app.
showTextAligned(...) is my own method that looks this way:
private void showTextAligned(String alignment,
PdfCanvas pdfCanvas,
String line,
float lineWidth,
float maxWidth) {
switch (alignment) {
case "CENTER": {
float shift = (maxWidth - lineWidth) / 2 - sumOfShifts;
pdfCanvas.moveText(shift, 0);
if(numberOfLines == 0) pdfCanvas.showText(line);
else pdfCanvas.newlineShowText(line);
numberOfLines++;
sumOfShifts += shift;
break;
}
case "RIGHT": {
float shift = maxWidth - lineWidth - sumOfShifts;
pdfCanvas.moveText(shift, 0);
if(numberOfLines == 0) pdfCanvas.showText(line);
else pdfCanvas.newlineShowText(line);
numberOfLines++;
sumOfShifts += shift;
break;
}
default:
pdfCanvas.moveText(0, 0);
if(numberOfLines == 0) pdfCanvas.showText(line);
else pdfCanvas.newlineShowText(line);
numberOfLines++;
break;
}
}
In my project, I used my variant, because it gives me an opportunity to work with hyphenation deeper (for instance, I can in future add functionality to avoid putting prepositions as a last word in the line).
I am working on a Dashing project using Sinatra, and I am displaying Graphs on the dashboard. I've been having a lot of trouble with displaying Timestamps on the X-Axis, Now that I have this working, I have another problem. On my graph, the x-axis keeps repeating the timestamp over and over again. What I mean by this is that it does not show any of the previous timestamps it just repeats the current timestamp over and over again, this is not what I want. Below is my code for the graph:
class Dashing.Graph extends Dashing.Widget
#accessor 'points', Dashing.AnimatedValue
#accessor 'current', ->
return #get('displayedValue') if #get('displayedValue')
points = #get('points')
if points
points[points.length - 1].y
#ready is triggered when ever the page is loaded.
ready: ->
container = $(#node).parent()
# Gross hacks. Let's fix this.
width = (Dashing.widget_base_dimensions[0] * container.data("sizex")) + Dashing.widget_margins[0] * 2 * (container.data("sizex") - 1)
height = (Dashing.widget_base_dimensions[1] * container.data("sizey"))
#graph = new Rickshaw.Graph(
element: #node
width: width
height: height
renderer: #get("graphtype")
series: [
{
color: "#fff",
data: [{x:0, y:0}]
}
]
)
#graph.series[0].data = #get('points') if #get('points')
format = (d) ->
months = new Array(12)
months[0] = "Jan"
months[1] = "Feb"
months[2] = "Mar"
months[3] = "Apr"
months[4] = "May"
months[5] = "Jun"
months[6] = "Jul"
months[7] = "Aug"
months[8] = "Sep"
months[9] = "Oct"
months[10] = "Nov"
months[11] = "Dec"
today = new Date()
month = today.getMonth()
day = today.getDate()
h = today.getHours()
m = today.getMinutes()
if(m <= 9)
d = months[month] + " " + day + " " + h + ":" + 0 + m
else
d = months[month] + " " + day + " " + h + ":" + m
x_axis = new Rickshaw.Graph.Axis.X(graph: #graph,pixelsPerTick: 100, tickFormat: format )
y_axis = new Rickshaw.Graph.Axis.Y(graph: #graph, tickFormat: Rickshaw.Fixtures.Number.formatKMBT)
#graph.render()
#onData is responsible for handling data sent from the jobs folder,
#any send_event methods will trigger this function
onData: (data) ->
if #graph
#graph.series[0].data = data.points
#graph.render()
node = $(#node)
value = parseInt data.points[data.points.length - 1].y
cool = parseInt node.data "cool"
warm = parseInt node.data "warm"
level = switch
when value <= cool then 0
when value >= warm then 4
else
bucketSize = (warm - cool) / 3 # Total # of colours in middle
Math.ceil (value - cool) / bucketSize
backgroundClass = "hotness#{level}"
lastClass = #get "lastClass"
node.toggleClass "#{lastClass} #{backgroundClass}"
#set "lastClass", backgroundClass
Can anyone help me understand why my graph does not want to show any previous values of X?
I think your issue is the call to today = new Date() in function format. The formatter is getting a date passed in by Rickshaw/D3, the date just needs to be formatted according to your needs. By calling today = new Date(), you are ignoring the passed in date and instead providing your own/new date.
Side note: you can take a look at D3's date/time formatting or moment.js for simpler date/time formatting, so your format function could shrink to 1 or 2 lines of code.
I'm using selenium to get some text on my webpage using xpath.
The page tag structure is as follows -
<span id="data" class="firefinder-match">
Seat Height, Laden
<sup>
<a class="speckeyfootnote" rel="p7" href="#">7</a>
</sup>
</span>
If I use the following code -
driver.findElement(By.xpath("//span[#id='data']")).getText();
I get the result = Seat Height, Laden 7
But I want to avoid reading the text within the <sup> tags and get the
result Seat Height, Laden
Please let me know which xpath expression I can use to get my desired result.
I don't know about any way to do this in Selenium, so there's my JS solution. The idea is to get all children of the element (including the text nodes) and then select only the text nodes. You might need to add some .trim() (or JS equivalent) calls to get rid of unneeded spaces.
The whole code:
WebElement elem = driver.findElement(By.id("data"));
String text;
if (driver instanceof JavascriptExecutor) {
text = ((JavascriptExecutor)driver).executeScript(
"var nodes = arguments[0].childNodes;" +
"var text = '';" +
"for (var i = 0; i < nodes.length; i++) {" +
" if (nodes[i].nodeType == Node.TEXT_NODE) {" +
" text += nodes[i].textContent;" +
" }" +
"}" +
"return text;"
, elem);
}
And just the JS for better readability.
var nodes = arguments[0].childNodes;
var text = '';
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].nodeType == Node.TEXT_NODE) {
text += nodes[i].textContent;
}
}
return text;