Skip to content

Instantly share code, notes, and snippets.

@tdewin
Last active June 29, 2026 09:55
Show Gist options
  • Select an option

  • Save tdewin/3e738c33de062646542cb5c7f673d80d to your computer and use it in GitHub Desktop.

Select an option

Save tdewin/3e738c33de062646542cb5c7f673d80d to your computer and use it in GitHub Desktop.
Dir with PNGs to Markdown
Sub ImportImagesToFullScreenAndBreak
Dim oDoc As Object
Dim oDrawPages As Object
Dim oNewPage As Object
Dim oFolderPicker As Object
Dim oSFA As Object
Dim sFolderUrl As String
Dim allFiles() As String
Dim fileList() As String
Dim fileCount As Long
Dim i As Long, j As Long, k As Long
Dim sTemp As String
Dim oShape As Object
Dim oSubShape As Object
Dim aSize As New com.sun.star.awt.Size
Dim aPosition As New com.sun.star.awt.Point
Dim oFrame As Object
Dim oDispatcher As Object
Dim oController As Object
Dim args(0) As New com.sun.star.beans.PropertyValue
' Font update variables
Dim bUpdateFont As Boolean
Dim sFontName As String
Dim iAns As Integer
' ==========================================
' CONFIGURATION & ADJUSTMENT VARIABLES
' ==========================================
Dim pxToUnit As Double
Dim targetWidth As Long
Dim targetHeight As Long
Dim posX As Long
Dim posY As Long
pxToUnit = 1920 / 28000
targetWidth = 1920 / pxToUnit
targetHeight = 1080 / pxToUnit
posX = 0
posY = 0
' ==========================================
oDoc = ThisComponent
oDrawPages = oDoc.getDrawPages()
oController = oDoc.getCurrentController()
oFrame = oController.getFrame()
oDispatcher = CreateUnoService("com.sun.star.frame.DispatchHelper")
' 1. Ask user about Font updates
bUpdateFont = False
iAns = MsgBox("Do you want to update all fonts in the broken-apart SVGs?", 4 + 32, "Font Update Setup")
If iAns = 6 Then ' 6 = Yes
sFontName = InputBox("Enter target font name:", "Select Font", "Arial")
If Trim(sFontName) <> "" Then
bUpdateFont = True
End If
End If
' 2. Open Folder Picker Dialog
oFolderPicker = CreateUnoService("com.sun.star.ui.dialogs.FolderPicker")
oFolderPicker.setDisplayDirectory(ConvertToURL(Environ("HOME") & "/Documents/"))
If oFolderPicker.execute() = 1 Then
sFolderUrl = oFolderPicker.getDirectory() & "/"
Else
Exit Sub
End If
' 3. Read directory using SimpleFileAccess
oSFA = CreateUnoService("com.sun.star.ucb.SimpleFileAccess")
allFiles = oSFA.getFolderContents(sFolderUrl, False)
fileCount = 0
For i = LBound(allFiles) To UBound(allFiles)
Dim sExt As String
sExt = LCase(Right(allFiles(i), 4))
If sExt = ".svg" Or sExt = ".png" Then
ReDim Preserve fileList(fileCount)
fileList(fileCount) = allFiles(i)
fileCount = fileCount + 1
End If
Next i
If fileCount = 0 Then
MsgBox "No SVG or PNG files found in the selected directory.", 48, "Error"
Exit Sub
End If
' 4. Alphabetically sort files (Bubble Sort)
For i = 0 To fileCount - 2
For j = i + 1 To fileCount - 1
If StrComp(fileList(i), fileList(j), 1) > 0 Then
sTemp = fileList(i)
fileList(i) = fileList(j)
fileList(j) = sTemp
End If
Next j
Next i
' 5. Process each file onto a new slide
For i = 0 To fileCount - 1
' Create a new slide and explicitly set its layout to Blank
oNewPage = oDrawPages.insertNewByIndex(oDrawPages.getCount())
oNewPage.Layout = 20 ' 20 represents the standard Blank Slide layout in LibreOffice Impress
oController.setCurrentPage(oNewPage)
If LCase(Right(fileList(i), 4)) = ".svg" Then
' --- SVG IMPORT & BREAK ---
args(0).Name = "FileName"
args(0).Value = fileList(i)
oDispatcher.executeDispatch(oFrame, ".uno:InsertGraphic", "", 0, args())
Wait 250
oShape = oNewPage.getByIndex(oNewPage.getCount() - 1)
aSize.Width = targetWidth
aSize.Height = targetHeight
oShape.setSize(aSize)
aPosition.X = posX
aPosition.Y = posY
oShape.setPosition(aPosition)
oController.select(oShape)
Wait 250
oDispatcher.executeDispatch(oFrame, ".uno:Break", "", 0, Array())
Wait 250
' --- OPTIONAL FONT UPDATE WORKER ---
' Once broken apart, the original graphic becomes a shape Group Object
If bUpdateFont Then
Dim oPageShapes As Object
Dim m As Long
oPageShapes = oNewPage
' Iterate backwards through all shapes created by the Break command
For k = oPageShapes.getCount() - 1 To 0 Step -1
oSubShape = oPageShapes.getByIndex(k)
' If it's a group shape generated by the break, check inside it
If oSubShape.supportsService("com.sun.star.drawing.GroupShape") Then
For m = 0 To oSubShape.getCount() - 1
Dim oChild As Object
oChild = oSubShape.getByIndex(m)
If oChild.PropertySetInfo.hasPropertyByName("CharFontName") Then
oChild.CharFontName = sFontName
End If
Next m
End If
' Check if the base or un-grouped shape itself supports text font formatting
If oSubShape.PropertySetInfo.hasPropertyByName("CharFontName") Then
oSubShape.CharFontName = sFontName
End If
Next k
End If
Else
' --- PNG STANDARD METHODS ---
oShape = oDoc.createInstance("com.sun.star.drawing.GraphicObjectShape")
oNewPage.add(oShape)
oShape.GraphicURL = fileList(i)
aSize.Width = targetWidth
aSize.Height = targetHeight
oShape.setSize(aSize)
aPosition.X = posX
aPosition.Y = posY
oShape.setPosition(aPosition)
End If
Next i
MsgBox "Successfully processed " & fileCount & " files!", 64, "Done"
End Sub
Sub MoveResizeFirstGraphicOnSlide
Dim oDoc As Object
Dim oController As Object
Dim oCurrentPage As Object
Dim oItem As Object
Dim aSize As New com.sun.star.awt.Size
Dim aPosition As New com.sun.star.awt.Point
oDoc = ThisComponent
oController = oDoc.getCurrentController()
' Get the currently active slide
oCurrentPage = oController.getCurrentPage()
' Check if the slide actually contains any shapes/images
If oCurrentPage.getCount() > 0 Then
' Grab the first object on the slide
oItem = oCurrentPage.getByIndex(0)
' Set width and height (9.2" x 5.62")
aSize.width = 23368
aSize.height = 14275
oItem.setsize(aSize)
' Set X and Y positions (0.4", 0")
aPosition.X = 1016
aPosition.Y = 0
oItem.setposition(aPosition)
Else
MsgBox "There are no objects on this slide."
End If
End Sub
Sub MoveNextAndResizeFirstGraphic
Dim oDoc As Object
Dim oController As Object
Dim oDrawPages As Object
Dim oCurrentPage As Object
Dim oNextPage As Object
Dim oItem As Object
Dim i As Integer
Dim aSize As New com.sun.star.awt.Size
Dim aPosition As New com.sun.star.awt.Point
oDoc = ThisComponent
oController = oDoc.getCurrentController()
oDrawPages = oDoc.getDrawPages()
' Find the index of the currently active slide
oCurrentPage = oController.getCurrentPage()
For i = 0 To oDrawPages.getCount() - 1
If oDrawPages.getByIndex(i).Name = oCurrentPage.Name Then
' Check if there actually is a next slide
If i < oDrawPages.getCount() - 1 Then
oNextPage = oDrawPages.getByIndex(i + 1)
' Change the active view to the next slide
oController.setCurrentPage(oNextPage)
oCurrentPage = oNextPage
Exit For
Else
MsgBox "You are already on the last slide."
Exit Sub
End If
End If
Next i
' Check if the new slide actually contains any shapes/images
If oCurrentPage.getCount() > 0 Then
' Grab the first object on the slide
oItem = oCurrentPage.getByIndex(0)
' Set width and height (9.2" x 5.62")
aSize.width = 23368
aSize.height = 14275
oItem.setsize(aSize)
' Set X and Y positions (0.4", 0")
aPosition.X = 1016
aPosition.Y = 0
oItem.setposition(aPosition)
Else
MsgBox "There are no objects on this slide."
End If
End Sub
# Prompt
# take find . -iname '*.png' adapt to sort by creation time, create markdown file, for each png create ## header and then insert image ![](a.png) so that it can be passed to pandoc to convert markdown to pptx
find . -iname '*.png' -print0 | xargs -0 stat -f "%B %N" | sort -n | cut -d' ' -f2- | while read -r img; do printf '## \n\n![](%s)\n\n' "$img"; done > presentation.md
# convert to presentation
pandoc presentation.md -o presentation.pptx

QSC

quick screen capture + code for converting a dir full of pngs to a markdown which can be ingested by pandoc

MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

# Initial Prompt
# This already exists cat ~/.qsc | jq
# ${
# "sleep": 1,
# "format": "screenshot-%date-%time-%uid.png",
# "outdir": ".",
# "uid": 1
#}
# Make zsh function that uses this info to do a screen capture. in this example, sleep 1 sec, output to current directory . and use file format "screenshot-%date-%time-%uid.png" where date is replaced by a date stamp 20260624 time is replaced with 110503 (24h clock, 11 o'clock 5min and 3 sec) and the %uid is replaced with the uid in the file +1 after which the script updates uid with this new number (basically the qsc stores the last used uid)
#
qsc_edit() {
local config_file="$HOME/.qsc"
vi $config_file && cat $config_file | jq
}
qsc_cont() {
# Ensure this is also blocked on non-macOS systems
if [[ "$OSTYPE" != "darwin"* ]]; then
echo "Error: This script is only for macOS." >&2
return 1
fi
# Check for the -s flag
local auto_stream=false
if [[ "$1" == "-s" ]]; then
auto_stream=true
fi
if [[ "$auto_stream" == "true" ]]; then
echo "Continuous stream started (using config sleep delay). Press [Ctrl+C] to quit."
else
echo "Continuous capture started. Press [ENTER] to take a screenshot, or [Ctrl+C] to quit."
fi
# Loop infinitely until interrupted
while true; do
if [[ "$auto_stream" == "true" ]]; then
# Fetch the sleep value from config dynamically to check its size
local config_file="$HOME/.qsc"
local sleep_val=$(jq -r '.sleep' "$config_file" 2>/dev/null || echo 0)
# Only auto-stream if sleep is strictly greater than 1
if (( sleep_val >= 1 )); then
qsc_capture
else
echo "\n[Warning] Sleep value ($sleep_val) <= 1. Reverting to manual prompt for safety."
read -r "?[Ready] "
qsc_capture
fi
else
# Default behavior: wait for Enter key
read -r "?[Ready] "
qsc_capture
fi
done
}
qsc_querydisplay() {
echo "hints displays"
system_profiler SPDisplaysDataType | grep Resolution
echo "use cmd+shift+5, select area if not last selection"
defaults read com.apple.screencapture
}
qsc_capture() {
# 1. Enforce macOS-only restriction
if [[ "$OSTYPE" != "darwin"* ]]; then
echo "Error: This script is only for macOS." >&2
return 1
fi
local config_file="$HOME/.qsc"
# 2. If config doesn't exist, create it with your defaults
if [[ ! -f "$config_file" ]]; then
echo "Configuration file not found. Creating default at $config_file"
cat << 'EOF' > "$config_file"
{
"sleep": 1,
"format": "screenshot-%date-%time-%uid.png",
"outdir": ".",
"uid": 1,
"x": 0,
"y": 0,
"width": 1280,
"height": 720
}
EOF
fi
# 3. Validate that jq is installed
if ! command -v jq &>/dev/null; then
echo "Error: 'jq' is required but not installed. Install it via 'brew install jq'." >&2
return 1
fi
# 4. Read values from the JSON config
local sleep_time=$(jq -r '.sleep' "$config_file")
local format_str=$(jq -r '.format' "$config_file")
local outdir=$(jq -r '.outdir' "$config_file")
local current_uid=$(jq -r '.uid' "$config_file")
# Read geometry coordinates
local crop_x=$(jq -r '.x' "$config_file")
local crop_y=$(jq -r '.y' "$config_file")
local crop_w=$(jq -r '.width' "$config_file")
local crop_h=$(jq -r '.height' "$config_file")
# 5. Increment and update the UID in the config file
local new_uid=$((current_uid + 1))
local temp_json=$(mktemp)
jq --argjson new_id "$new_uid" '.uid = $new_id' "$config_file" > "$temp_json" && mv "$temp_json" "$config_file"
# 6. Generate dynamic timestamps
local date_stamp=$(date +'%Y%m%d')
local time_stamp=$(date +'%H%M%S')
# 7. Construct the filename string (escaping the % character)
local filename="$format_str"
filename="${filename//\%date/$date_stamp}"
filename="${filename//\%time/$time_stamp}"
filename="${filename//\%uid/$(printf "%04d" $new_uid)}"
echo $filename
local save_path="${outdir}/${filename}"
# 8. Execute the sleep delay
echo "Sleeping for ${sleep_time}s before capture..."
sleep "$sleep_time"
# 9. macOS Capture Region
# -R flags x,y,width,height
# -x keeps it silent (no camera shutter sound)
screencapture -x -R "${crop_x},${crop_y},${crop_w},${crop_h}" "$save_path"
echo "Area screenshot (${crop_w}x${crop_h} at ${crop_x},${crop_y}) saved to: $save_path"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment